/**
 * 
 */
package guice.command;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;

/**
 * All command in a system are registered with this entity.
 * 
 * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
 * @since 1.0
 * @version $Id$
 */
@Singleton
public class DefaultCommandRegistry implements CommandRegistry {

	private static final Log log = LogFactory.getLog(DefaultCommandRegistry.class);

	@Inject
	private static Injector injector;

	private static final Object mutex = new Object();

	/**
	 * Cached {@link Store} instances keyed on Entity Class.
	 */
	private static Map<Class<? extends ICommand>, Provider<?>> cache = new HashMap<Class<? extends ICommand>, Provider<?>>();

	/**
	 * Give the Command Request Data class, and Command Response class, the
	 * registry looks up the appropriate ICommand implementation.
	 * 
	 * @param <REQ>
	 * @param <RESP>
	 * @param requestClass
	 * @param responseClass
	 * @return the ICommand implementation that can handle the specified request
	 *         and response types. A <code>null</code> is returned if no
	 *         appropriate command could be found.
	 */
	@Override
	public <REQ, RESP> ICommand<REQ, RESP> getCommandInstance(final Class<REQ> requestClass,
	        final Class<RESP> responseClass) {

		long startTime = System.nanoTime();
		try {
			ParameterizedType parameterizedCommandType = new ParameterizedType() {

				public Type[] getActualTypeArguments() {
					// Here we tell the runtime that one of these Type arguments
					// is
					// another ParameterizedType. Example: Store<Project,
					// Query<Project>>
					return new Type[] { requestClass, responseClass };
				}

				public Type getOwnerType() {
					return null;
				}

				public Type getRawType() {
					return ICommand.class;
				}
			};

			TypeLiteral<?> typeLiteral = TypeLiteral.get(parameterizedCommandType);

			// Check if there is a Store Provider available
			// TODO: Implement caching!
			if (injector.findBindingsByType(typeLiteral).size() > 0) {
				Provider<?> provider = injector.findBindingsByType(typeLiteral).get(0).getProvider();
				return (ICommand<REQ, RESP>) provider.get();
			}

			// Unable to find any command implementation that can handle the
			// specified Command Request and produce the specified result.
			return null;

		} finally {
			long endTime = System.nanoTime();
			log
			        .debug(String
			                .format(
			                        "%s nanoseconds taken to obtain ICommand Provider instance for Command Request '%s' and Command Response '%s'",
			                        (endTime - startTime), requestClass.getSimpleName(), responseClass.getSimpleName()));
		}
	}
}
